Skip to content

fix: the update card stops demanding a redeploy the edge just hasn't caught up with - #34

Merged
karngyan merged 1 commit into
mainfrom
fix/update-card-propagation
Aug 9, 2026
Merged

fix: the update card stops demanding a redeploy the edge just hasn't caught up with#34
karngyan merged 1 commit into
mainfrom
fix/update-card-propagation

Conversation

@karngyan

@karngyan karngyan commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

The repro

Deploy the relay from the Remote screen. Every checkmark lands — token verified, worker deployed, web app uploaded — and the card underneath still says "update the relay." Click it, and the second deploy is a byte-for-byte no-op. Wait a minute instead, and the card quiets down on its own. Two deploys happened in the maintainer's repro; time was the actual fix.

The mechanism

The card compares this daemon's deployStamp() (release version, or on dev builds a content hash of the Worker module + web assets) against deployedVersion(), which does a live GET <origin>/api/health and returns the FLUE_VERSION the currently-serving Worker reports. A deploy returns when the Cloudflare API accepts the new Worker — but the edge keeps serving the previous one for seconds, occasionally longer. During that propagation window /api/health still answers with the old stamp, so Status() faithfully reports a version skew that is really just replication lag, and the UI offers an update that already happened.

What changed

The daemon knows what it just shipped — it stops throwing that away. relayUIService now remembers (origin, stamp) after a successful Provision or Update, and Status() answers from that memory instead of the transient health read — but only while the memory stays true:

  • A failed deploy sets nothing. It changed nothing at the edge, so the card keeps offering what the health read supports.
  • A binary whose own deployStamp() changed (rebuilt/upgraded daemon) makes the memory stale — the health read wins again, so a genuinely newer build still shows its update card. Within one process the stamp is constant, so this guard mostly states the invariant; it costs one comparison.
  • The memory is keyed to the origin it shipped to. SetAddress (or a relay join under a running daemon) moves cfg.Origin, and the memory self-invalidates rather than vouching for a relay this process never deployed to — the Worker behind a repointed name should be the same one, but that's the health read's fact to confirm, not memory's to assume. SetAddress itself needed no code change; its comment now says the interaction is deliberate.
  • A daemon restart drops the memory, deliberately — propagation is long done by the time a daemon comes back, and the health read is telling the truth again.

The memory lives under its own mutex, not the deploy-serialising mu, so Status() renders during a deploy instead of queueing behind one. The RelayUIStatus.DeployedVersion doc-comment in internal/daemon/relayui.go was updated to stay truthful.

The CLI paths need nothing. flue relay setup and flue relay update are their own process: they deploy, print the checkmarks, and exit — they never serve Status(), so there is no card for them to mislead. They cannot populate the daemon's memory either (different process), which means a CLI update followed within seconds by opening the Remote screen can still see the lag — a much narrower window, self-healing, and not worth a persistence mechanism whose staleness would then need managing.

One accepted trade-off, stated plainly: while the memory holds, this daemon does no live health checks — so another machine deploying a different version to the same Worker mid-lifetime goes unnoticed until this daemon restarts or rebuilds. That's the rare case, and a wrong "up to date" there is strictly less harmful than the wrong "update me" this fixes for every deploy.

Test evidence

Three new tests in cmd/flue/relayui_test.go, driven through the same fake Cloudflare and a health server that answers the previous stamp (what a real mid-propagation edge does):

  • TestRelayUIStatusTrustsItsOwnDeployWhileTheEdgeCatchesUp — before the deploy the health read decides (the card shows); after a successful Update and after a Provision, Status reports this binary's stamp even though the edge still answers the old one.
  • TestRelayUIFailedUpdateLeavesTheHealthReadInCharge — a deploy the API refused earns no memory; the card logic is unchanged.
  • TestRelayUIStaleShipMemoryLosesToTheHealthRead — a planted stamp mismatch (the rebuilt-binary shape) falls back to the health read; a real SetAddress repoint leaves neither memory nor health speaking for the new origin.
$ make web relay && go vet ./... && go test ./...
ok  github.com/karnstack/flue/cmd/flue          5.053s
ok  github.com/karnstack/flue/internal/daemon   5.922s
... (all packages pass)

🤖 Generated with Claude Code

…t caught up with

A deploy returns when Cloudflare's API accepts the new Worker, but the
edge keeps serving the previous one for seconds — occasionally longer.
During that window the relay's /api/health still answers with the old
FLUE_VERSION stamp, so Status(), which compared deployStamp() against
that live read, put the "update the relay" card right back under the
checkmarks the deploy just earned. The user obliges and redeploys
identical bytes; time was the actual fix.

The daemon knows what it just shipped — stop throwing that away.
relayUIService now remembers (origin, stamp) after a successful
Provision or Update, and Status answers from that memory instead of the
health read, but only while the memory stays true: a failed deploy sets
nothing, a binary whose own deployStamp changed falls back to the health
read so a genuinely newer build still gets its card, and the memory is
keyed to the origin it shipped to, so SetAddress (or a re-join under a
running daemon) self-invalidates it. A daemon restart drops the memory,
deliberately — propagation is long done by then.

The CLI paths (flue relay setup / update) run in their own process,
print, and exit; they never serve Status, so they need no memory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@karngyan
karngyan merged commit 24e957b into main Aug 9, 2026
1 check passed
@karngyan
karngyan deleted the fix/update-card-propagation branch August 9, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant